/***
 *
 *  This class implements the specifications described in Dr. Tan's
 *  Lab 4 in his Operating System course. It simulates bank accounts
 *  with multiple people withdrawing and depositing into the account.
 *  For more information, read that lab description.
 *
 ***/

#include "bank_transactor.h"

/* Default Constructor */
Bank_Transactor :: Bank_Transactor () {
	random = new Random_Generator ();

	num_threads = 0;
	num_accounts = 0;
	num_transactions = 0;
	max_num_threads = 1000;
}

/* Destructor */
Bank_Transactor :: ~Bank_Transactor () {

}

/* Main algorithm implemented here */
void Bank_Transactor :: run () {
	Bank * bank = new Bank ();
	list <My_Thread_Class *> clients;
	int * accounts;
	int * accounts_amt;


	/* See if user wants to seed the random number generator */
	if (ask_seed_random ()) {
		cout << "Seed 1 - ";
		int seed1 = get_seed ();

		cout << "Seed 2 - ";
		int seed2 = get_seed ();

		random -> set_seed (seed1, seed2);
	}

	/* Get user inputs for our program */
	get_num_clients ();
	get_num_accounts ();
	get_num_transactions_per_client ();

	accounts = new int [num_accounts]; // account id info
	accounts_amt = new int [num_accounts]; // account amounts

	/* Threads get spawned here */
	// Load up the bank accounts and account ids
	for (int i = 0; i < num_accounts; i++) {
		// The account amount
		int amount = get_account_amount ();
		accounts_amt [i] = amount;

		// The account id
		int id = 100000 + random -> get_random (900000);
		accounts [i] = id;
	}

	bank -> set_accounts (accounts, accounts_amt, num_accounts);

	// Spawn threads to run the clients
	for (int i = 0; i < num_threads; i++) {
		My_Thread_Class * thread = new Client (accounts, num_transactions, num_accounts);
		((Client *) thread) -> set_thread_number (i);
		((Client *) thread) -> set_bank (bank);
		((Client *) thread) -> set_rand (random);
		clients.push_front (thread);

		cout << "got here" << endl;

		if (!clients.front () -> start_thread ()) {
			cout << "Problem starting a thread" << endl;
			exit (1);
		}

		// Make sure we don't have too many threads going at once
		if ((int) clients.size () == max_num_threads) {
			if (!clients.back () -> wait_for_thread ()) {
				cout << "Problem joining thread" << endl;
				exit (1);
			}

			delete clients.back ();
			clients.pop_back ();
		}
	}

	// Now make sure we let the rest of the threads finish
	while (clients.size () > 0) {
		if (!clients.back () -> wait_for_thread ()) {
			cout << "Problem joining thread" << endl;
			exit (1);
		}

		delete clients.back ();
		clients.pop_back ();
	}

	delete random;
	delete bank;
	delete [] accounts;
	delete [] accounts_amt;
}

/* Asks user to input how many people will be accessing each account */
void Bank_Transactor :: get_num_clients () {
	string input;

	try {
		// Get user input
		cout << "Input the number of clients (threads): "; cin >> input;

		// Check input for errors
		if (is_integer (input)) {
			num_threads = atoi (input.c_str ());
			if (num_threads < 1) throw num_threads;
		} else {
			throw input;
		}
	} catch (string s) {
		cout << "\"" << s << "\"" << " is not a valid integer\n\n";
		get_num_clients ();
	} catch (int value) {
		cout << endl << "Cannot have less than 1 thread: " << value << "\n\n";
		get_num_clients ();
	}
}

/* Asks user how many accounts each client will be accessing */
void Bank_Transactor :: get_num_accounts () {
	string input;

	try {
		// Get user input
		cout << "Input the number of accounts: "; cin >> input;

		// Check input for errors
		if (is_integer (input)) {
			num_accounts = atoi (input.c_str ());
			if (num_accounts < 1) throw num_accounts;
		} else {
			throw input;
		}
	} catch (string s) {
		cout << "Not a valid integer: " << s << endl;
		get_num_accounts ();
	} catch (int num) {
		cout << endl << "Cannot have less than 1 account: " << num << endl;
		get_num_accounts ();
	}
}

/* Asks users how many transactions each client will commit */
void Bank_Transactor :: get_num_transactions_per_client () {
	string input;

	try {
		// Get user input
		cout << "Input the number of transactions per account: "; cin >> input;

		// Check input for errors
		if (is_integer (input)) {
			num_transactions = atoi (input.c_str ());
			if (num_transactions < 1) throw num_transactions;
		} else {
			throw input;
		}
	} catch (string s) {
		cout << "Not a valid integer: " << s << endl;
		get_num_transactions_per_client ();
	} catch (int num) {
		cout << endl << "Cannot have less than 1 transaction: " << num << endl;
		get_num_transactions_per_client ();
	}
}

/***
 *
 *  Checks if string passed in is a valid integer. It also checks to make
 *  sure that the input isn't larger than the biggest integer value that we
 *  can have.  Finally, it will accept commas and underscores to separate
 *  every third value.
 *
 ***/
bool Bank_Transactor :: is_integer (string num) {
	string input;
	bool is_neg = false;
	bool result = true;

	// If num is negative, strip the '-' sign
	if (num [0] == '-') {
		input.insert (0, num.substr (1, num.length () - 1));
		is_neg = true;
	} else {
		input = num;
	}

	for (int i = 0; i < (int) input.length (); i++) {
		if (!is_digit (input [i])) {
			result = false;
		}
	}

	if (atoi (input.c_str ()) > INT_MAX) {
		result = false;
	}

	return result;
}

/* Ensures that the character passed is a digit */
bool Bank_Transactor :: is_digit (char c) {
	bool result;

	if (c == '0') {
		result = true;
	} else if (c == '1') {
		result = true;
	} else if (c == '2') {
		result = true;
	} else if (c == '3') {
		result = true;
	} else if (c == '4') {
		result = true;
	} else if (c == '5') {
		result = true;
	} else if (c == '6') {
		result = true;
	} else if (c == '7') {
		result = true;
	} else if (c == '8') {
		result = true;
	} else if (c == '9') {
		result = true;
	} else {
		result = false;
	}

	return result;
}

bool Bank_Transactor :: ask_seed_random () {
	char input;
	bool result;

	do {
		cout << "Would you like to seed the random number generator (y/n)? "; cin >> input;

		if (input != 'y' && input != 'Y' && input != 'n' && input != 'N') {
			cout << "Invalid input..." << endl << endl;;
		}

	} while (input != 'y' && input != 'Y' && input != 'n' && input != 'N');

	if (input == 'y' || input == 'Y') {
		result = true;
	} else {
		result = false;
	}

	return result;
}

long Bank_Transactor :: get_seed () {
	string input;
	long result;

	try {
		// Get user input
		cout << "Enter seed for random number generator: "; cin >> input;

		// Check input for errors
		if (is_integer (input)) {
			result = (long) atof (input.c_str ());
		} else {
			throw input;
		}
	} catch (string s) {
		cout << "Not a valid integer: " << s << endl;
		get_seed ();
	}

	return result;
}

int Bank_Transactor :: get_account_amount () {
	string input;
	int result;

	try {
		// Get user input
		cout << "Input the account amount: "; cin >> input;

		// Check input for errors
		if (is_integer (input)) {
			result = atoi (input.c_str ());
		} else {
			throw input;
		}
	} catch (string s) {
		cout << "Not a valid integer: " << s << endl;
		get_account_amount ();
	}

	return result;
}

